﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using EnvSafe.Expression;
using Heroius.Extension;
using Heroius.Files;

namespace Ranpage
{
    public class Engine
    {
        public Engine()
        {
            interpreter.RegistCommonSymbolAndVariableMapping();
        }

        public Group Source { get; set; } = new Group() { Name = "原分组" };

        public Group Result { get; set; } = new Group() { Name = "目标分组" };

        public RuleSet Rules { get; set; } = new RuleSet();

        public void ImportSource(string filename)
        {
            EntitySilo silo = new EntitySilo();
            silo.AutoStore = false;
            silo.StoreElements = true;
            silo.Recursion = true;
            silo.Load(filename);
            Source.Members.Clear();
            Source.SubGroups.Clear();
            silo.Fetch("List", Source);
        }

        public void ExportSource(string filename)
        {
            EntitySilo silo = new EntitySilo();
            silo.AutoStore = false;
            silo.StoreElements = true;
            silo.Recursion = true;
            silo.Store("List", Source);
            silo.Save(filename);
        }

        public void ImportRules(string filename)
        {
            EntitySilo silo = new EntitySilo();
            silo.AutoStore = false;
            silo.StoreElements = true;
            silo.Recursion = true;
            silo.Load(filename);
            Rules.Rules.Clear();
            silo.Fetch("Rules", Rules);
        }

        public void ExportRules(string filename)
        {
            EntitySilo silo = new EntitySilo();
            silo.AutoStore = false;
            silo.StoreElements = true;
            silo.Recursion = true;
            silo.Store("Rules", Rules);
            silo.Save(filename);
        }

        public void ExportResult(string filename)
        {
            EntitySilo silo = new EntitySilo();
            silo.AutoStore = false;
            silo.StoreElements = true;
            silo.Recursion = true;
            silo.Store("List", Result);
            silo.Save(filename);
        }

        public void MainGo()
        {
            ExecutingContext context = new ExecutingContext(this);
            Result.Properties.Clear();
            Result.Members.Clear();
            Result.SubGroups.Clear();
            Execute(0, context);
        }

        /// <summary>
        /// 公式构建器
        /// </summary>
        Interpreter interpreter = new Interpreter();

        /// <summary>
        /// 迭代函数：从指定的位置开始计算
        /// </summary>
        /// <param name="beginStep">要计算的位置，0-based</param>
        /// <param name="context">上下文</param>
        /// <returns>执行到的下一位置（未执行的）</returns>
        public int Execute(int beginStep, ExecutingContext context)
        {
            int step = beginStep;
            for (; step < Rules.Rules.Count; step++)
            {
                var cmd = Rules.Rules[step];
                switch (cmd.Command)
                {
                    case Rule.Commands.Skip:
                        break;
                    case Rule.Commands.Create_Group_In:
                        {
                            var g = new Group() { Name = cmd.Params[0].Value };
                            context.Group_Cache[cmd.Params[1].Value].SubGroups.Add(g);
                            context.Group_Cache.Add(g.Name, g);
                        }
                        break;
                    case Rule.Commands.Create_Groups_In:
                        {
                            var cnt = cmd.Params[2].Value.As<int>();
                            for (int i = 0; i < cnt; i++)
                            {
                                var g = new Group() { Name = $"{cmd.Params[0].Value}_{i + 1}" };
                                context.Group_Cache[cmd.Params[1].Value].SubGroups.Add(g);
                                context.Group_Cache.Add(g.Name, g);
                            }
                        }
                        break;
                    case Rule.Commands.Clear_Empty_Groups:
                        {
                            ClearEmptyGroups(Source, context.Group_Cache);
                            ClearEmptyGroups(Result, context.Group_Cache);
                            for (int i = 0; i < context.Group_Cache.Count; i++)
                            {
                                var g = context.Group_Cache.ElementAt(i).Value;
                                ClearEmptyGroups(g, context.Group_Cache);
                                if (g.Members.Count == 0 && g.SubGroups.Count == 0)
                                {
                                    context.Group_Cache.Remove(g.Name);
                                    i--;
                                }
                            }
                        }
                        break;

                    case Rule.Commands.Mark_Persion_In:
                        {
                            var from = context.Group_Cache[cmd.Params[0].Value];
                            context.Person_Mark_Cache.Add(new Tuple<Person, Group>(from.Members.First(m => m.Name == cmd.Params[1].Value), from));
                        }
                        break;
                    case Rule.Commands.Mark_Order_In:
                        {
                            var cnt = cmd.Params[1].Value.As<int>();
                            var person_pick_from = context.Group_Cache[cmd.Params[0].Value];
                            for (int i = 0; i < Math.Min(cnt, person_pick_from.Members.Count); i++)
                            {
                                context.Person_Mark_Cache.Add(new Tuple<Person, Group>(person_pick_from.Members[i], person_pick_from));
                            }
                        }
                        break;
                    case Rule.Commands.Mark_All_In:
                        {
                            var person_pick_from = context.Group_Cache[cmd.Params[0].Value];
                            for (int i = 0; i < person_pick_from.Members.Count; i++)
                            {
                                context.Person_Mark_Cache.Add(new Tuple<Person, Group>(person_pick_from.Members[i], person_pick_from));
                            }
                        }
                        break;
                    case Rule.Commands.Mark_Random_In:
                        {
                            var cnt = cmd.Params[1].Value.As<int>();
                            var from = context.Group_Cache[cmd.Params[0].Value];
                            var copy = from.ShallowCopy();
                            Random r = new Random();
                            var min = Math.Min(cnt, from.Members.Count);
                            for (int i = 0; i < min; i++)
                            {
                                int j = r.Next(0, copy.Members.Count);
                                context.Person_Mark_Cache.Add(new Tuple<Person, Group>(copy.Members[j], from));
                                copy.Members.RemoveAt(j);
                            }
                        }
                        break;

                    case Rule.Commands.Filter_Marked_Property_Match:
                        {
                            var proc = interpreter.GenerateProcedure(cmd.Params[1].Value);
                            for (int i = context.Person_Mark_Cache.Count - 1; i > 0; i--)
                            {
                                var r = proc.Calculate(new Dictionary<string, string>() {
                                    {
                                        cmd.Params[0].Value,
                                        context.Person_Mark_Cache[i].Item1.Properties.First(p => p.Key == cmd.Params[0].Value).Value
                                    }
                                });
                                if (r.As<int>() <= 0)
                                    context.Person_Mark_Cache.RemoveAt(i);
                            }
                        }
                        break;
                    case Rule.Commands.Move_All_Marked_To:
                        {
                            for (int i = context.Person_Mark_Cache.Count - 1; i >= 0; i--)
                            {
                                context.Person_Mark_Cache[i].Item2.Members.Remove(context.Person_Mark_Cache[i].Item1);
                                context.Group_Cache[cmd.Params[0].Value].Members.Add(context.Person_Mark_Cache[i].Item1);
                                context.Person_Mark_Cache.RemoveAt(i);
                            }
                        }
                        break;
                    case Rule.Commands.Move_Marked_To:
                        {
                            var cnt = cmd.Params[0].Value.As<int>();
                            var min = Math.Min(cnt, context.Person_Mark_Cache.Count);
                            for (int i = 0; i < min; i++)
                            {
                                var p = context.Person_Mark_Cache.First();
                                context.Person_Mark_Cache.RemoveAt(0);
                                p.Item2.Members.Remove(p.Item1);
                                context.Group_Cache[cmd.Params[1].Value].Members.Add(p.Item1);
                            }
                        }
                        break;
                    case Rule.Commands.Clear_Marked:
                        {
                            context.Person_Mark_Cache.Clear();
                        }
                        break;

                    case Rule.Commands.Select_Order_Group_In_As:
                        {
                            var cnt = cmd.Params[1].Value.As<int>();
                            var pool = context.Group_Cache[cmd.Params[0].Value].SubGroups;
                            var min = Math.Min(cnt, pool.Count);
                            List<Group> selected = new List<Group>();//避免迭代中集合变化的副本
                            for (int i = 0; i < min; i++)
                            {
                                selected.Add(pool[i]);
                            }
                            for (int i = 0; i < min; i++)
                            {
                                var g = selected[i];
                                if (context.Group_Cache.ContainsKey(cmd.Params[2].Value))
                                {
                                    context.Group_Cache[cmd.Params[2].Value] = g;
                                }
                                else
                                {
                                    context.Group_Cache.Add(cmd.Params[2].Value, g);
                                }
                                Execute(step + 1, context);
                            }
                        }
                        break;
                    case Rule.Commands.Select_Random_Group_In_As:
                        {
                            var cnt = cmd.Params[1].Value.As<int>();
                            var copy = context.Group_Cache[cmd.Params[0].Value].ShallowCopy();
                            var min = Math.Min(cnt, copy.SubGroups.Count);
                            Random r = new Random();
                            List<Group> selected = new List<Group>();
                            for (int i = 0; i < min; i++)
                            {
                                var j = r.Next(0, copy.SubGroups.Count);
                                selected.Add(copy.SubGroups[j]);
                                copy.SubGroups.RemoveAt(j);
                            }
                            for (int i = 0; i < min; i++)
                            {
                                var g = selected[i];
                                if (context.Group_Cache.ContainsKey(cmd.Params[2].Value))
                                {
                                    context.Group_Cache[cmd.Params[2].Value] = g;
                                }
                                else
                                {
                                    context.Group_Cache.Add(cmd.Params[2].Value, g);
                                }
                                Execute(step + 1, context);
                            }
                        }
                        break;

                    case Rule.Commands.Traversal_Order_Groups_In_As:
                        {
                            var copy = context.Group_Cache[cmd.Params[0].Value].ShallowCopy();
                            for (int i = 0; i < copy.SubGroups.Count; i++)
                            {
                                var g = copy.SubGroups[i];
                                if (context.Group_Cache.ContainsKey(cmd.Params[1].Value))
                                {
                                    context.Group_Cache[cmd.Params[1].Value] = g;
                                }
                                else
                                {
                                    context.Group_Cache.Add(cmd.Params[1].Value, g);
                                }
                                Execute(step + 1, context);
                            }
                        }
                        break;
                    case Rule.Commands.Traversal_Random_Group_In_As:
                        {
                            var copy = context.Group_Cache[cmd.Params[0].Value].ShallowCopy();
                            var cnt = copy.SubGroups.Count;
                            Random r = new Random();
                            List<Group> sorted = new List<Group>();
                            for (int i = 0; i < cnt; i++)
                            {
                                int j = r.Next(0, copy.SubGroups.Count);
                                sorted.Add(copy.SubGroups[j]);
                                copy.SubGroups.RemoveAt(j);
                            }
                            for (int i = 0; i < cnt; i++)
                            {
                                var g = sorted[i];
                                if (context.Group_Cache.ContainsKey(cmd.Params[1].Value))
                                {
                                    context.Group_Cache[cmd.Params[1].Value] = g;
                                }
                                else
                                {
                                    context.Group_Cache.Add(cmd.Params[1].Value, g);
                                }
                                Execute(step + 1, context);
                            }
                        }
                        break;

                    //case Rule.Commands.If_Member_Property_Match:
                    //    break;
                    //case Rule.Commands.If_Group_Property_Match:
                    //    break;
                    //case Rule.Commands.If_Member_Amount_Match_In:
                    //    break;
                    //case Rule.Commands.If_Group_Amount_Match_In:
                    //    break;

                    case Rule.Commands.Struct_End:
                        return step + 1;
                    default:
                        throw new Exception("命令不支持：未实现处理");
                }
            }
            return step;
        }

        /// <summary>
        /// 迭代函数：构建组缓存
        /// </summary>
        /// <param name="orig"></param>
        /// <param name="cache"></param>
        public void BuildGroupCache(Group orig, Dictionary<string, Group> cache)
        {
            cache.Add(orig.Name, orig);
            foreach (var item in orig.SubGroups)
            {
                BuildGroupCache(item, cache);
            }
        }
        /// <summary>
        /// 迭代函数：清除空组
        /// </summary>
        /// <param name="orig"></param>
        /// <param name="cache"></param>
        public void ClearEmptyGroups(Group orig, Dictionary<string, Group> cache)
        {
            for (int i = orig.SubGroups.Count - 1; i >= 0; i--)
            {
                var g = orig.SubGroups[i];
                if (g.Members.Count == 0)
                {
                    if (g.SubGroups.Count > 0)
                    {
                        ClearEmptyGroups(orig.SubGroups[i], cache);
                    }
                    if (g.SubGroups.Count == 0)
                    {
                        if (cache.ContainsKey(orig.SubGroups[i].Name))
                            cache.Remove(orig.SubGroups[i].Name);
                        orig.SubGroups.RemoveAt(i);
                    }
                }
            }
        }
    }

    /// <summary>
    /// 执行上下文，保持过程缓存
    /// </summary>
    public class ExecutingContext
    {
        /// <summary>
        /// 执行上下文，保持过程缓存
        /// </summary>
        public ExecutingContext(Engine engine)
        {
            Core = engine;
            Core.BuildGroupCache(Core.Source, Group_Cache);
            Core.Result.Members.Clear();
            Core.Result.SubGroups.Clear();
            Group_Cache.Add(Core.Result.Name, Core.Result);
        }

        /// <summary>
        /// 引擎引用
        /// </summary>
        public Engine Core;

        /// <summary>
        /// 分组全局缓存：冗余
        /// </summary>
        public Dictionary<string, Group> Group_Cache = new Dictionary<string, Group>();

        /// <summary>
        /// 标记成员缓存
        /// </summary>
        public List<Tuple<Person, Group>> Person_Mark_Cache = new List<Tuple<Person, Group>>();
    }
}
